package com.osdp.common.security.config;

import com.osdp.auth.entity.User;
import com.osdp.auth.server.ILoginService;
import com.osdp.auth.server.ITokenService;
import com.osdp.common.constant.CommonConstant;
import com.osdp.common.base.JWTInfo;
import com.osdp.common.util.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;

import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * OAuth2Server 配置
 *
 * https://zhuanlan.zhihu.com/p/403379055
 * https://zhuanlan.zhihu.com/p/403674303
 * https://github.com/spring-projects/spring-authorization-server
 * https://docs.spring.io/spring-security/reference/
 * 名词说明：
 * （1）Third-party application：第三方应用程序，本文中又称"客户端"（client），比如打开知乎，使用第三方登录，选择qq登录，这时候知乎就是客户端。
 * （2）HTTP service：HTTP服务提供商，本文中简称"服务提供商"，即上例的qq。
 * （3）Resource Owner：资源所有者，本文中又称"用户"（user）,即登录用户。
 * （4）User Agent：用户代理，本文中就是指浏览器。
 * （5）Authorization server：认证服务器，即服务提供商专门用来处理认证的服务器。
 * （6）Resource server：资源服务器，即服务提供商存放用户生成的资源的服务器。它与认证服务器，可以是同一台服务器，也可以是不同的服务器。
 *  执行流程：
 * （A）用户打开客户端以后，客户端要求用户(Resource Owner)给予授权。
 * （B）用户(Resource Owner)同意给予客户端授权。
 * （C）客户端使用上一步获得的授权，向认证服务器(Authorization server)申请令牌。
 * （D）认证服务器对客户端进行认证以后，确认无误，同意发放令牌。
 * （E）客户端使用令牌，向资源服务器(Resource server)申请获取资源。
 * （F）资源服务器确认令牌无误，同意向客户端开放资源。
 * 5授权模式:
 * （1）授权码模式（authorization code）
 * （2）简化模式（implicit）
 * （3）密码模式（resource owner password credentials）
 * （4）客户端模式（client credentials）
 */
@Configuration
@EnableAuthorizationServer
@Slf4j
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private ITokenService tokenService;

    @Autowired
    private ILoginService loginService;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private AuthenticationManager authenticationManager;

    private final static String CLIENT_CREDENTIALS = "client_credentials";

    private String getUsername(HttpServletRequest request){
        return request.getParameter("username");
    }

    /**
     * 授予（grant)类型
     * @param request
     * @return
     */
    private String getGrantType(HttpServletRequest request){
        return request.getParameter("grant_type");
    }

    private boolean isClientCredentials(String grantType){
        return StringUtils.equals(CLIENT_CREDENTIALS,grantType);
    }

    /**
     * token 管理
     * @return
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return ((accessToken, authentication) -> {
            if(accessToken instanceof DefaultOAuth2AccessToken){
                DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken)accessToken;
                Map<String,Object> additionalInformation = new LinkedHashMap<>();
                String grantType = getGrantType(SpringContextUtil.getHttpServletRequest());
                String tokenInfo = null;
                if(isClientCredentials(grantType)){
                    String username = getUsername(SpringContextUtil.getHttpServletRequest());
                    if(StringUtils.isNotEmpty(username)){
                        tokenInfo = loginService.unverifiedLogin(username,false);
                    }
                }else{
                    User user = (User)authentication.getPrincipal();
                    JWTInfo info = new JWTInfo(user.getId(),user.getUsername(),user.isSuperAdmin(),user.getTenantId(),user.getTenantIds(),user.getRoleCodes());
                    tokenInfo = tokenService.create(info);
                }
                if(StringUtils.isNotEmpty(tokenInfo)){
                    additionalInformation.put(CommonConstant.HEADER_ACCESS_TOKEN, tokenInfo);
                    token.setAdditionalInformation(additionalInformation);
                }
            }
            return accessToken;
        });
    }

//    @Bean
//    public PasswordEncoder passwordEncoder(){
//        return DefaultPasswordEncoder.getInstance();
//    }

    /**
     * 检查 token 的策略，即配置令牌端点的安全约束,就是这个端点谁能访问，谁不能访问
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()") //获取令牌，允许所有人
                .checkTokenAccess("permitAll()") //检测令牌，允许所有人
                // 允许表单认证,这个如果配置支持allowFormAuthenticationForClients的，且url中有client_id和client_secret的会走ClientCredentialsTokenEndpointFilter来保护
                // 如果没有支持allowFormAuthenticationForClients或者有支持但是url中没有client_id和client_secret的，走basic认证保护
                .allowFormAuthenticationForClients(); //通过表单来提交获取token
    }

    // 指定客户端信息的数据库来源
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }

    /**
     * OAuth2 的主配置信息
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
                .tokenEnhancer(tokenEnhancer());
    }
}
